package com.roadmap.struts.action;

import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Set;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.log4j.Logger;
import org.apache.struts.Globals;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionRedirect;

import com.roadmap.biz.common.IManager;
import com.roadmap.biz.message.NoticeManager;
import com.roadmap.cache.RdcSessionCache;
import com.roadmap.client.sns.FriendshipManager;
import com.roadmap.common.RdcResourceBundle;
import com.roadmap.entities.Aspirant;
import com.roadmap.exception.EntityNotFoundException;
import com.roadmap.exception.InvalidQueryException;
import com.roadmap.exception.InvalidValueException;
import com.roadmap.exception.NullObjectException;
import com.roadmap.exception.ServerException;
import com.roadmap.struts.action.auth.LogoffAction;
import com.roadmap.struts.action.auth.LogonAction;
import com.roadmap.util.RdcEncryptor;

/** In this class, some common methods are defined to be used in sub-actions. */
public abstract class RdcAction extends Action implements IActionUtil {
	private final static Logger logger = Logger.getLogger(RdcAction.class);

	private final static Set<String> notUpdatedDataAction = new HashSet<String>();

	/**
	 * when the same form is being tried to submit for times, such case will be
	 * handled and then forward to a specific page.
	 */
	private String smtForward;

	static {
		notUpdatedDataAction.add(LogoffAction.class.getSimpleName());
		notUpdatedDataAction.add(LogonAction.class.getSimpleName());
	}

	/** a forward used for handling duplicate submittals */
	public RdcAction(String smtForward) {
		this.smtForward = smtForward;
	}

	/**
	 * return the forward which is used in this way. when the same form is being
	 * tried to submit for times, such case will be handled and then forward to
	 * a specific page.
	 */
	public String getSmtForward() {
		return smtForward;
	}

	/**
	 * This method will check if the same action form is already submitted and
	 * processed. If submitted, return true, or else return false.
	 * 
	 * @return: true - the form is submitted again. false - the form is not
	 *          submitted yet
	 */
	public boolean isFormSubmitted(HttpServletRequest request) throws Exception {
		// basically, the parameter "request" should not be NULL. Just in case.
		if (request == null) {
			throw new Exception();
		}

		// use token to prevent the same form being submitted for times.
		// if the token is null or it's valid, it means the form is not
		// submitted yet
		String savedToken = (String) request.getSession().getAttribute(
				Globals.TRANSACTION_TOKEN_KEY);
		if (savedToken == null || this.isTokenValid(request)) {
			// token is valid
			if (savedToken == null) {
				this.saveToken(request);
			}
			else {
				this.resetToken(request);
			}

			return false;
		}
		else {
			return true;
		}
	}

	/**
	 * save cookies.
	 * 
	 * @param cooks:
	 *            a set of cookie names
	 * @param values:
	 *            a set of cookie values
	 * @param isEncrypted:
	 *            a set of booleans to decide if the values are encrypted in
	 *            cookies
	 */
	public void saveCookies(HttpServletResponse response, String[] cooks,
			String[] values, Boolean[] isEncrypted) {
		if (response == null || cooks == null || values == null
				|| cooks.length != values.length || isEncrypted == null
				|| isEncrypted.length != values.length) {
			logger.warn("invalid cookie data. ");
			return;
		}

		for (int i = 0; i < cooks.length; i++) {
			String value = (isEncrypted[i]) ? RdcEncryptor.getInstace().encode(
					values[i]) : values[i];
			Cookie cook = new Cookie(cooks[i], value);
			cook.setMaxAge(ActionConst.Cookie_Save_Time);
			response.addCookie(cook);
		}
	}

	/**
	 * remove cookies
	 * 
	 * @param cooks:
	 *            a set of cookie names
	 */
	public void removeCookie(HttpServletResponse response, String[] cooks) {
		if (response == null || cooks == null) {
			logger.warn("invalid cookie data. ");
			return;
		}
		for (int i = 0; i < cooks.length; i++) {
			Cookie cook = new Cookie(cooks[i], "expired");
			cook.setMaxAge(0);
			response.addCookie(cook);
		}
	}

	/**
	 * check user's permission to execute the action. (1) check if the visitor
	 * is the owner;(2) if the visitor is not the item owner, check if they're
	 * friends
	 */
	public boolean checkPermission(HttpServletRequest request)
			throws NullObjectException, InvalidQueryException, ServerException,
			InvalidValueException, EntityNotFoundException {
		String uid = request.getParameter(ActionConst.Aspirant_Id_In_Request);
		// if UID doesn't exist in request or is empty, use another way which
		// will be implemented in sub actions to check the permission
		if (uid == null || uid.trim().length() == 0) {
			// 将当前用户的称呼放在request里
			request.setAttribute(ActionConst.Aspirant_Name_In_Request,
					RdcResourceBundle.ME_IN_CHINESE);
			return true;
		}

		String aid = getUIDFromSession(request);
		// 如果当前用户不是访问项的拥有者
		if (!uid.equalsIgnoreCase(aid)) {
			// 验证当前用户是否是拥有者的好友
			FriendshipManager mgr = new FriendshipManager(uid);
			// 查到被访问者的真实姓名
			Aspirant host = (Aspirant) mgr.getOwner();
			if (host != null) {
				request.setAttribute(ActionConst.Aspirant_Name_In_Request, host
						.getRealName());
			}
			else {
				EntityNotFoundException e = new EntityNotFoundException();
				logger.error("User was not found by id " + uid, e);
				throw e;
			}

			if (mgr.get(aid) == null) {
				return false;
			}
		}
		else {
			// 将当前用户的称呼放在request里
			request.setAttribute(ActionConst.Aspirant_Name_In_Request,
					RdcResourceBundle.ME_IN_CHINESE);
		}
		return true;
	}

	public final ActionForward execute(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response)
			throws Exception {
		ActionForward forward = getErrorForward(mapping, form, request,
				response);
		return (forward == null) ? performAction(mapping, form, request,
				response) : forward;
	}

	/**
	 * The real logic for handling the request. Sub-actions should implement
	 * this method in which real things will be done
	 */
	public abstract ActionForward performAction(ActionMapping mapping,
			ActionForm form, HttpServletRequest request,
			HttpServletResponse response) throws Exception;

	/**
	 * this function is not necessary in the actions any more. will use Tomcat
	 * to do the job
	 */
	public boolean isLoggedOn(HttpServletRequest request) {
		if (getUIDFromSession(request) != null) {
			return true;
		}

		// 将用户要访问的url记录下来，并放在session里
		StringBuffer url = request.getRequestURL();
		String queryString = request.getQueryString();
		if (queryString != null && !queryString.trim().equals("")) {
			url.append("?");
			url.append(request.getQueryString());
		}

		request.getSession().setAttribute(ActionConst.USER_ORIGINAL_URL,
				url.toString());

		if (logger.isDebugEnabled()) {
			logger.debug("user doesn't log on!");
		}

		return false;
	}

	public ActionForward getErrorForward(ActionMapping mapping,
			ActionForm form, HttpServletRequest request,
			HttpServletResponse response) throws Exception {
		ActionErrors errors = new ActionErrors();
		ActionForward forward = null;
		boolean isCheckPassed = true;

		if (getSmtForward() != null && isFormSubmitted(request)) {
			forward = mapping.findForward(getSmtForward());
			isCheckPassed = false;

			if (logger.isDebugEnabled() && getSmtForward() != null) {
				logger.debug("duplicated submit!");
			}
		}
		else if (!isLoggedOn(request)) {
			ActionRedirect redirect = new ActionRedirect("/logon.do");
			redirect.setRedirect(true);
			forward = redirect;
			isCheckPassed = false;
		}
		else if (!checkPermission(request)) {
			forward = mapping.findForward("getlock");
			isCheckPassed = false;

			if (logger.isDebugEnabled()) {
				logger
						.debug("user doesn't have rights to access the resource!");
			}
		}
		else if (isOwner(request)) {
			request.setAttribute(ActionConst.Visitor_Is_Owner, true);
		}

		// if the check is not passed, forward should not be NULL
		if (!isCheckPassed && forward == null) {
			Exception ex = new Exception();
			logger
					.error("checking doesn't pass but the forward to handle the failure is NULL.");
			throw ex;
		}

		// save errors
		if (!errors.isEmpty()) {
			saveErrors(request, errors);
		}

		if (forward == null) {
			// no errors then update data in the session
			updateDataInSession(request);
		}

		return forward;
	}

	/**
	 * set the UID stored in request with the one in session and redirect with
	 * current user's id. This method will be called by those actions which need
	 * "uid" as a parameter in request url.
	 */
	protected ActionRedirect handleInvalidUser(HttpServletRequest request,
			String action) {
		ActionRedirect redirect = new ActionRedirect(action);
		redirect.setRedirect(true);
		redirect.addParameter(ActionConst.Aspirant_Id_In_Request,
				getUIDFromSession(request));
		return redirect;
	}

	/**
	 * verify the visitor owns the items which the visitor is viewing. check if
	 * the uid in request URL equals to the one in session
	 */
	public boolean isOwner(HttpServletRequest request) {
		if (request == null) {
			return false;
		}

		// if the request holds the attribute, the checking was already taken.
		Object obj = request.getAttribute(ActionConst.Visitor_Is_Owner);
		if (obj != null) {
			return true;
		}

		// the current visitor's id is stored in the session
		String hostId = request
				.getParameter(ActionConst.Aspirant_Id_In_Request);
		if (hostId == null) {
			return true;
		}

		// the host of items is stored in the request URL
		String visitId = this.getUIDFromSession(request);
		if (logger.isDebugEnabled()) {
			logger.debug("the visitor is " + visitId);
			logger.debug("the host is " + hostId);
		}
		return hostId.equals(visitId);
	}

	public void encodingString(Object text) {
		if (text == null) {
			return;
		}

		Field[] fields = text.getClass().getDeclaredFields();
		for (int i = 0; i < fields.length; i++) {
			if (fields[i].getType() == String.class) {
				try {
					fields[i].setAccessible(true);
					String value = (String) fields[i].get(text);
					if (value != null) {
						value = new String(value.getBytes("iso8859-1"), "utf-8");
						fields[i].set(text, value);
					}
				}
				catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}

	/** get the user id from session */
	public String getUIDFromSession(HttpServletRequest request) {
		if (request != null) {
			return (String) request.getSession().getAttribute(
					ActionConst.Aspirant_Id_In_Session);
		}
		return null;
	}

	/**
	 * update data stored in session
	 * 
	 * @throws NullObjectException
	 */
	public void updateDataInSession(HttpServletRequest request)
			throws ServerException, NullObjectException {
		if (request == null) {
			throw new NullObjectException("request is NULL.");
		}

		// when executing some actions, don't update the session data
		if (notUpdatedDataAction.contains(this.getClass().getSimpleName())) {
			return;
		}
		HttpSession session = request.getSession();
		RdcSessionCache cache = (RdcSessionCache) session
				.getAttribute(ActionConst.RDC_SESSION_CACHE_NAME);
		if (cache == null) {
			cache = new RdcSessionCache();
			session.setAttribute(ActionConst.RDC_SESSION_CACHE_NAME, cache);
		}

		String uid = getUIDFromSession(request);
		// uid should be the first condition
		if (uid != null && cache.needUpdated()) {
			NoticeManager mgr = new NoticeManager(uid);
			mgr.getAmountsOfNewInformation(cache);
		}
	}

	protected int getCurrentPageNum(HttpServletRequest request) {
		if (request == null) {
			return 0;
		}

		int currentPage = 1;
		String num = (String) request.getParameter(ActionConst.PAGING_PARAM);
		if (num != null) {
			try {
				currentPage = Integer.valueOf(num);
			}
			catch (Exception e) {
				currentPage = 1;
			}
		}

		if (currentPage < 1) {
			currentPage = 1;
		}

		return currentPage;
	}

	protected void createPagingInfoInRequest(HttpServletRequest request,
			IManager mgr) {
		if (request == null || mgr == null) {
			return;
		}

		PagingInfo pf = new PagingInfo(request, mgr.getPage(), mgr
				.getMaxResultCount());
		if (pf.getTotalPageCount() > 1) {
			request.setAttribute(ActionConst.PAGING_INFO_IN_REQUEST, pf);
		}
	}
}
